package com.nulldev.util.security.encryptAPI.impls.experimental.shifter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.ByteUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.data.DataTransferAPI.DataTransfer;
import com.nulldev.util.data.DataTransferAPI.DataTransferWriter;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.security.encryptAPI.impls.experimental.shifter.misc.CyclicHash;
import com.nulldev.util.security.encryptAPI.impls.experimental.shifter.misc.RabinKarpHash;

public class Shifter2 {

	private static int calcShifter(final String text, final String password) {
		return calcShifter(text.getBytes(), password);
	}

	private static int calcShifter(final byte[] text, final String password) {
		int offset = 0;
		// offset[low]
		for (final char c : password.toCharArray()) {
			offset += c;
		}
		offset /= password.length();
		final int pObjOff = offset;
		// offset[high]
		for (int i = 0; i < password.length(); i++) {
			offset |= password.charAt(i) >> (i + 1);
			offset |= i << pObjOff & (i + 1);
		}
		// offset[pass-1]
		final CyclicHash ch = new CyclicHash(32);
		for (int i = 0; i < password.length(); i++) {
			offset &= ch.eat(password.charAt(i));
		}
		// offset[pass-2]
		final RabinKarpHash rch = new RabinKarpHash(32);
		for (int i = password.length() - 1; i > 0; i--) {
			offset ^= rch.eat(password.charAt(i));
		}
		return offset;
	}

	private static long operateOn(final long data, final long totalValue, final long index) {
		return (data + (totalValue << index) + (index % totalValue)) + (index ^ totalValue);
	}

	private static long unOperateOn(final long data, final long totalValue, final long index) {
		return (data - (totalValue << index) - (index % totalValue)) - (index ^ totalValue);
	}

	public static byte[] encryptBytes(final byte[] data, final String password) {
		final long[] dat = new long[data.length];
		final int totalValue = calcShifter(new String(data), password);
		for (int i = 0; i < data.length; i++) {
			dat[i] = operateOn(data[i], totalValue, i);
		}
		final int reverseO = totalValue % 4;
		reassign(dat, reverseO);
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		final DataTransferWriter dtw = new DataTransferWriter(baos);
		for (final long _dat : dat) {
			try {
				dtw.writeLong(_dat);
			} catch (IOException e) {
				IOUtils.closeQuietly(dtw);
				throw new RuntimeException(e);
			}
		}
		IOUtils.closeQuietly(dtw);
		return baos.toByteArray();
	}

	public static byte[] encryptString(final String data, final byte[] password) {
		return encryptBytes(password, new String(password));
	}

	public static byte[] encryptString(final String data, final String password) {
		final long[] dat = new long[data.length()];
		final int totalValue = calcShifter(data, password);
		for (int i = 0; i < data.length(); i++) {
			dat[i] = operateOn(data.charAt(i), totalValue, i);
		}
		final int reverseO = totalValue % 4;
		reassign(dat, reverseO);
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		final DataTransferWriter dtw = new DataTransferWriter(baos);
		for (final long _dat : dat) {
			try {
				dtw.writeLong(_dat);
			} catch (IOException e) {
				IOUtils.closeQuietly(dtw);
				throw new RuntimeException(e);
			}
		}
		IOUtils.closeQuietly(dtw);
		return baos.toByteArray();
	}

	public static byte[] decryptBytes(final byte[] data, final byte[] password) {
		return decryptBytes(data, new String(password));
	}

	public static byte[] decryptBytes(final byte[] data, final String password) {
		final int totalValue = calcShifter(data, password);
		final int l = data.length / 8;
		final long[] dat = new long[l];
		final ByteArrayInputStream bais = new ByteArrayInputStream(data);
		final DataTransfer dt = new DataTransfer(bais);
		for (int i = 0; i < l; i++) {
			try {
				dat[i] = dt.readLong();
			} catch (IOException e) {
				IOUtils.closeQuietly(dt);
				throw new RuntimeException(e);
			}
		}
		final int reverseO = totalValue % 4;
		reassign(dat, reverseO);
		for (int i = 0; i < dat.length; i++) {
			dat[i] = unOperateOn(dat[i], totalValue, i);
		}
		IOUtils.closeQuietly(dt);
		return ByteUtil.directConvert(dat);
	}

	public static String decryptString(final byte[] data, final String password) {
		final int totalValue = calcShifter(data, password);
		final int l = data.length / 8;
		final long[] dat = new long[l];
		final ByteArrayInputStream bais = new ByteArrayInputStream(data);
		final DataTransfer dt = new DataTransfer(bais);
		for (int i = 0; i < l; i++) {
			try {
				dat[i] = dt.readLong();
			} catch (IOException e) {
				IOUtils.closeQuietly(dt);
				throw new RuntimeException(e);
			}
		}
		final int reverseO = totalValue % 4;
		reassign(dat, reverseO);
		for (int i = 0; i < dat.length; i++) {
			dat[i] = unOperateOn(dat[i], totalValue, i);
		}
		IOUtils.closeQuietly(dt);
		return new String(ByteUtil.directConvert(dat));
	}

	private static void reassign(final long[] dat, int reverseO) {
		if (reverseO < 0)
			reverseO /= -1;
		if (reverseO <= 1 && dat.length % 2 == 0) {
			reverseO = 5;
		} else if (reverseO <= 1 && dat.length % 2 != 0) {
//			System.arraycopy(MathUtil.swapFirstAndSecondHalf(dat), 0, dat, 0, dat.length);
			return;
		} else {
			final long[][] lists = ArrayUtils.splitArray(dat, dat.length / reverseO);
			if (lists == null)
				throw new NullPointerException("lists == null!");
			switch (reverseO) {
				case 2: {
					final long[] l1 = lists[0];
					final long[] l2 = lists[1];
					final long[] l3 = lists.length >= 3 ? lists[2] : null;
					if (l3 != null) {
						lists[0] = l3;
						lists[1] = l2;
						lists[2] = l1;
					} else {
						lists[0] = l2;
						lists[1] = l1;
					}
					break;
				}
				case 3: {
					final long[] l1 = lists[0];
					final long[] l2 = lists[1];
					final long[] l3 = lists[2];
					final long[] l4 = lists.length >= 4 ? lists[3] : null;
					if (l4 != null) {
						lists[0] = l3;
						lists[1] = l4;
						lists[2] = l1;
						lists[3] = l2;
					} else {
						lists[0] = l3;
						lists[1] = l2;
						lists[2] = l1;
					}
					break;
				}
				case 5: {
					final long[] l1 = lists[0];
					final long[] l2 = lists[1];
					final long[] l3 = lists[2];
					final long[] l4 = lists[3];
					final long[] l5 = lists[4];
					lists[0] = l5;
					lists[1] = l2;
					lists[2] = l4;
					lists[3] = l3;
					lists[4] = l1;
					break;
				}
				default:
					throw new UnsupportedOperationException("Unknown reverse: " + reverseO);
			}
			System.arraycopy(ArrayUtils.merge(lists), 0, dat, 0, dat.length);
		}
	}

	public static void main(String[] args) {
		final String t = "Hello world!";
		final String password = StringsUtil.randomString(128);
		JVM.println("Password: " + password);
		final byte[] b = Shifter2.encryptString(t, password);
		JVM.println("Encrypted: (" + b.length + ")");
		JVM.println(new String(b));
		final byte[] d = Shifter2.decryptBytes(b, password);
		JVM.println("Decrypted: (" + d.length + ")");
		final String tx = new String(d);
		JVM.println(tx);
		if (!tx.equals(t)) {
			JVM.println("[!] Failed to decrypt, bailing out.");
			FastIO.get().flush();
			JVM.exit(-1);
			return;
		}
		final boolean attemptCracking = true;
		if (attemptCracking) {
			final long maxAttempts = Long.MAX_VALUE;
			long attempt = 0;
			for (; attempt < maxAttempts; attempt++) {
				final String p = StringsUtil.randomString(128);
				final byte[] df = Shifter2.decryptBytes(b, p);
				final String e = new String(df);
				if (e.equalsIgnoreCase(t)) {
					JVM.println("Cracked!");
					JVM.println("Attempt #: " + attempt);
					JVM.println("Password: " + p);
					JVM.println("Output: " + e);
					break;
				} else {
					JVM.println("Failed: " + e);
				}
			}
			if (attempt == maxAttempts) {
				JVM.println("Failed to crack.");
			}
			FastIO.get().flush();
			JVM.exit(-1);
		} else {
			FastIO.get().flush();
			JVM.exit(-1);
		}
	}
}
